
import re


class FindTitleLine:
    """判断传入的行是否是标题行

    作用是给类对象传入一行，返回一个数字，0代表非标题行，其他代表（比如2）
    相应级别的标题（2级标题##）

    除了判断行内是否存在标题标志(#)以外，还需要考虑排除代码块内的行，
    比如python代码块内的注释会以#号开头, 跟标题混淆。

    markdown 代码块的语法有两种：
    1. 一个空行开始或标题行开始，之后每行前面一个制表符或者四个空格起始。
    2. 由反三引号 ``` 包裹
    """

    def __init__(self):
        self.state = self.state_nomal
        self.preview_line_is_empty = True
        # 缩进格式代码块在紧跟标题行可以不需要空行
        self.preview_line_is_title = False

        # <<<<< 以下是一些用于行内检索的正则表达式
        # 检索标题符 ##
        self.r_title = re.compile(r"^\s*(?P<sign>#+).+")
        # 判断空行或者仅包含空白符的行
        self.r_not_empty = re.compile(r"\S+")
        # 判断缩进格式的代码块
        self.r_code_block_indent = re.compile(
            r"^(?:[ ]{4}|(?:[ ]{,3}\t))(?=\s*\S+)")
        # 判断反引号格式的代码块
        self.r_code_block_quate_start = re.compile(r"^\s*```[^`]*")
        self.r_code_block_quate_end = re.compile(r"^\s*```[^`\S]*")

    def _check_title_line(self, line: str):
        """判断 line 是否是标题行

        如果是标题行，返回标题级别(int)，否则返回0
        """
        capture = self.r_title.search(line)
        if capture is None:
            self.preview_line_is_title = False
            return 0  # 非标题行
        sign = capture.group("sign")
        return len(sign)  # 返回标题级别

    def state_code_block_indent(self, line: str):
        """缩进格式的代码块状态

        这个状态下的任务是判断传进来的行是否是缩进代码行，否的话就转换到 normal 状态，
        并且还要在 normal 状态下分析。
        """
        capture = self.r_code_block_indent.search(line)
        if capture is not None:
            return 0

        # 到这里缩进代码块结束了，本行不属于代码块，转换到 normal 状态
        self.state = self.state_nomal
        # ！line 可以是标题行，或者三引号标记的代码块开始，所以要下一步
        # 放心，此行不会从normal状态又转回来造成死循环
        return self.state(line)

    def state_code_block_backquote(self, line: str):
        """以反三引号标记的代码块状态

        这个状态下的任务是找到代码块结束的标志，并把状态转换到 normal 模式
        """
        capture = self.r_code_block_quate_end.search(line)
        if capture is None:
            return 0
        # 到这里说明 line 是反三引号代码块的结束符，转换到 normal 模式
        self.state = self.state_nomal
        return 0

    def state_nomal(self, line: str):
        """普通状态

        此状态下的任务是判断:
        是否是标题行，是否要转换到其他模式，并负责按需转换到其他模式
        """
        # 判断缩进格式代码块
        if self.preview_line_is_empty or self.preview_line_is_title:
            self.preview_line_is_empty = False
            capture = self.r_code_block_indent.search(line)
            if capture is not None:  # 表示该行是缩进格式的代码块行
                self.state = self.state_code_block_indent
                return 0

        # 判断是否是反引号包括的代码块
        capture = self.r_code_block_quate_start.search(line)
        if capture is not None:
            self.state = self.state_code_block_backquote
            return 0
        # 判断是否是标题行
        title_level = self._check_title_line(line)
        if title_level > 0:  # 找到标题
            self.preview_line_is_title = True
            return title_level
        # 到这里说明line 就一普通行
        self.preview_line_is_title = False
        return 0

    def __call__(self, line) -> int:
        """
        返回值：标题的level
        """
        if self.r_not_empty.search(line) is None:  # line 是空行
            if self.state == self.state_nomal:
                self.preview_line_is_empty = True
            return 0
        return self.state(line)


if __name__ == "__main__":
    f = FindTitleLine()
    path = "example.md"
    with open(path, encoding="utf8") as fr:
        lines = fr.readlines()
    for line in lines:
        level = f(line)
        if level > 0:
            print(line.rstrip())
